import { path } from 'ramda';
import set from 'lodash/set';
import upperFirst from 'lodash/upperFirst';
import { gqcPermissionCheck } from '@kuiper/auth';
import nestedToOneLevelPath from '@kuiper-helpers/nested-to-one-level-path';
import urlPathConcat from '@kuiper-helpers/url-path-concat/dist';
import fetch from 'fermionjs/lib/fetch';
import pkg from '../../../../package.json';

import {
	JOB_TASK_TYPES,
	AVILIABLE_TASK_MAP,
	TASK_CATEGORY_TYPES_MAP,
	TASK_TYPE_CATEGORY_MAP,
	TASK_CATEGORY_KEYS,
} from '../../../common/constants';
import findByIdsEmpty from './findByIdsEmpty';
import removeJobById from './removeById';
import removeJobByIds from './removeByIds';
import updateJob from './update';
import createOne from './createOne';
import dailyAverageAvailability from './dailyAverageAvailability';
import escapeRegExp from '../../helpers/escapeRegExp';
const { name } = pkg;

export function categorySecurityBuilder({ record, asset, tasks }) {
	const ret = TASK_CATEGORY_KEYS.reduce((obj, category) => {
		obj[category] =
			category === 'asset'
				? {
						enable: asset?.taskSettings?.enable,
						status: asset?.resultId?.securityStatus || 'unknown',
						warningCount: asset?.resultId?.result?.warningCount || 0,
						updateTime: asset?.resultId?.endTime,
				  }
				: {
						enable: false,
						status: 'unknown',
						warningCount: 0,
						updateTime: null,
				  };
		return obj;
	}, {});

	const taskCategoryMap = {};
	tasks.forEach(task => {
		const taskCategory = TASK_TYPE_CATEGORY_MAP[task.taskType];
		taskCategoryMap[taskCategory] = taskCategoryMap[taskCategory] || [];
		taskCategoryMap[taskCategory].push(task);
	});
	Object.entries(taskCategoryMap).forEach(([category, tasks]) => {
		const taskTypes = TASK_CATEGORY_TYPES_MAP[category];
		ret[category].enable = taskTypes.some(type =>
			[]
				.concat(record[`${type}Settings`] || [])
				.some(setting => setting?.enable)
		);

		let updateTime = null;
		const warningObj = {};
		const warnings = tasks.reduce((arr, t) => {
			// eslint-disable-next-line no-unused-expressions
			t.result?.warnings?.forEach(w => {
				if (w.isResolved || w.ignoreTime) return;
				arr.push(w);
			});
			warningObj[t.taskType] = t.result?.warnings?.length;
			if (
				new Date(t.endTime || 0).getTime() > new Date(updateTime || 0).getTime()
			) {
				updateTime = t.endTime;
			}
			return arr;
		}, []);
		ret[category].warningDetails = warningObj;
		ret[category].updateTime = updateTime;

		ret[category].status = warnings.length > 0 ? 'warning' : 'safe';
		ret[category].warningCount = warnings.length;

		const securityStatusArr = taskTypes.reduce((arr, t) => {
			return arr.concat(record[`${t}SecurityStatus`]);
		}, []);

		const isUnknown = securityStatusArr.every(x => x === 'unknown');
		if (isUnknown) {
			ret[category].status = 'unknown';
		}
	});
	return ret;
}

export default server => {
	const {
		app: {
			schemaComposer,
			model: { Job },
		},
	} = server;

	const tc = server.methods.registerGraphQLSchema({
		name: 'Job',
		model: Job,
		queryFields: {
			Many: 'findMany',
			One: 'findOne',
			Count: 'count',
			Pagination: 'pagination',
		},
		mutationFields: {
			CreateMany: 'createMany',
			RemoveOne: 'removeOne',
			RemoveMany: 'removeMany',
		},
		onPreAddFields(tc) {
			const {
				app: {
					graphqlResolvers,
					schemaComposer,
					schema: { Task, User, Asset },
					config: {
						[name]: { cydEnabledApi, token },
					},
				},
			} = server;

			// 过滤时不传入Array
			tc.mongooseResolvers
				.findMany()
				.args.filter.type.setField('httpSettings', 'JobHttpSettingsInput');
			tc.mongooseResolvers
				.findMany()
				.args.filter.type.setField('pingSettings', 'JobPingSettingsInput');

			tc.addRelation('user', {
				resolver: () => User.mongooseResolvers.dataLoader({ lean: true }),
				prepareArgs: {
					_id: x => x.uid,
				},
				projection: { uid: 1 },
			});

			tc.addRelation('asset', {
				resolver: () => Asset.mongooseResolvers.dataLoader({ lean: true }),
				prepareArgs: {
					_id: x => x.assetId,
				},
				projection: { assetId: 1 },
			});

			JOB_TASK_TYPES.forEach(type => {
				if (AVILIABLE_TASK_MAP[type]) {
					tc.addRelation(`${type}Result`, {
						resolver: () =>
							Task.mongooseResolvers
								.dataLoaderMany({ lean: true })
								.wrapResolve(findByIdsEmpty),
						prepareArgs: {
							_ids: source => (source[`${type}ResultId`] || []).filter(Boolean),
						},
						projection: { [`${type}ResultId`]: 1 },
					});
					tc.addRelation(`${type}PrevResult`, {
						resolver: () =>
							Task.mongooseResolvers
								.dataLoaderMany({ lean: true })
								.wrapResolve(findByIdsEmpty),
						prepareArgs: {
							_ids: source =>
								(source[`${type}PrevResultId`] || []).filter(Boolean),
						},
						projection: { [`${type}PrevResultId`]: 1 },
					});
					tc.addRelation(`${type}Status`, {
						resolver: () =>
							Task.mongooseResolvers
								.dataLoaderMany({ lean: true })
								.wrapResolve(findByIdsEmpty),
						prepareArgs: {
							_ids: source => (source[`${type}StatusId`] || []).filter(Boolean),
						},
						projection: { [`${type}StatusId`]: 1 },
					});
					return;
				}

				tc.addRelation(`${type}Result`, {
					resolver: () => Task.mongooseResolvers.dataLoader({ lean: true }),
					prepareArgs: {
						_id: source => source[`${type}ResultId`],
					},
					projection: { [`${type}ResultId`]: 1 },
				});
				tc.addRelation(`${type}PrevResult`, {
					resolver: () => Task.mongooseResolvers.dataLoader({ lean: true }),
					prepareArgs: {
						_id: source => source[`${type}PrevResultId`],
					},
					projection: { [`${type}PrevResultId`]: 1 },
				});
				tc.addRelation(`${type}Status`, {
					resolver: () => Task.mongooseResolvers.dataLoader({ lean: true }),
					prepareArgs: {
						_id: source => source[`${type}StatusId`],
					},
					projection: { [`${type}StatusId`]: 1 },
				});

				graphqlResolvers.Query.JobPagination = graphqlResolvers.Query.JobPagination.addFilterArg(
					{
						name: `${type}Settings`,
						type: `Job${upperFirst(type)}SettingsInput`,
						description: 'target',
						query: (query, value) => {
							Object.assign(
								query,
								nestedToOneLevelPath(value, `${type}Settings`)
							);
						},
					}
				);
				graphqlResolvers.Query.JobMany = graphqlResolvers.Query.JobMany.addFilterArg(
					{
						name: `${type}Settings`,
						type: `Job${upperFirst(type)}SettingsInput`,
						description: 'target',
						query: (query, value) => {
							Object.assign(
								query,
								nestedToOneLevelPath(value, `${type}Settings`)
							);
						},
					}
				);
				graphqlResolvers.Query.JobCount = graphqlResolvers.Query.JobCount.addFilterArg(
					{
						name: `${type}Settings`,
						type: `Job${upperFirst(type)}SettingsInput`,
						description: 'target',
						query: (query, value) => {
							Object.assign(
								query,
								nestedToOneLevelPath(value, `${type}Settings`)
							);
						},
					}
				);
			});

			tc.addRelation('inTaskQueue', {
				prepareArgs: {
					record: source => source,
				},
				projection: { _id: true, assetId: true },
				resolver: () =>
					schemaComposer.createResolver({
						name: 'inTaskQueueCN',
						type: 'Boolean',
						args: {
							record: 'JSON',
						},
						resolve: async ({ args: { record }, context: { request } }) => {
							const {
								server: {
									app: {
										model: { Task },
									},
								},
							} = request;

							const { _id, assetId } = record;
							const task = await Task.findOne({
								$and: [
									{ $or: [{ refId: _id }, { refId: assetId }] },
									{ $or: [{ status: 'waiting' }, { status: 'active' }] },
								],
							});
							if (task) {
								return true;
							}
							return false;
						},
					}),
			});
			tc.addRelation('cydEnabled', {
				prepareArgs: {
					_id: source => source._id,
					uid: source => source.uid,
					cydEnabled: source => source.cydEnabled,
				},
				projection: {
					_id: true,
					uid: true,
					cydEnabled: true,
				},
				resolver: () =>
					schemaComposer.createResolver({
						name: 'cydEnabledByNewPy',
						type: 'Boolean',
						args: {
							uid: 'MongoID',
							_id: 'MongoID',
							cydEnabled: 'Boolean',
						},
						resolve: async ({
							args: { _id, uid, cydEnabled },
							context: { request },
						}) => {
							console.log('[ cydEnabledApi ]-309', cydEnabledApi);
							const api = urlPathConcat(cydEnabledApi);
							const body = JSON.stringify({
								uid,
								job_id: _id,
							});
							request.log.debug(
								{ api, body, cydEnabled },
								'同步cydEnabled状态'
							);
							return fetch(api, {
								method: 'POST',
								headers: {
									'Content-Type': 'application/json',
									Cookie: `token=${token}`,
								},
								timeout: 2000,
								body,
							})
								.then(x => {
									return x.cyd_enabled;
								})
								.catch(error => {
									request.log.error(
										{ api, body, cydEnabled, error },
										'同步cydEnabled状态失败'
									);
									return cydEnabled;
								});
						},
					}),
			});
			tc.addRelation('averageAvailability', {
				prepareArgs: {
					record: source => source,
				},
				projection: {
					_id: true,
				},
				resolver: () =>
					schemaComposer.createResolver({
						name: 'dailyAverageAvailabilityCN',
						type: 'JSON',
						args: {
							record: 'JSON',
						},
						resolve: dailyAverageAvailability,
					}),
			});

			tc.addRelation('categoryStatus', {
				prepareArgs: {
					record: source => source,
				},
				projection: JOB_TASK_TYPES.reduce(
					(obj, x) => {
						obj[`${x}ResultId`] = true;
						obj[`${x}Settings`] = true;
						return obj;
					},
					{ assetId: true, ignoredWarnIdMap: true }
				),
				resolver: () =>
					schemaComposer.createResolver({
						name: 'categorySecurityResolver',
						type: schemaComposer.createObjectTC(`
							type JobCategoryStatusPayload {
								${TASK_CATEGORY_KEYS.map(
									category => `
										${category}: JobCategoryStatus
									`
								).join('\n')}
							}


							type JobCategoryStatus {
								enable: Boolean
								status: String
								warningCount: Int
								updateTime: Date
								warningDetails: JSON
							}
						`),
						args: {
							record: 'JSON',
						},
						resolve: async ({ args: { record }, context: { request } }) => {
							const {
								server: {
									app: {
										model: { Asset, Task },
									},
								},
							} = request;

							const asset = await Asset.findById(record.assetId)
								.populate('resultId')
								.lean();
							const jobTaskIds = record.httpResultId
								.concat(record.pingResultId || [])
								.concat(record.vulResultId || [])
								.concat(record.sslResultId || [])
								.concat(record.contentResultId || [])
								.concat(record.securityEventResultId || []);
							const tasks = await Task.find({
								_id: { $in: jobTaskIds },
							}).lean();
							return categorySecurityBuilder({ record, asset, tasks });
						},
					}),
			});

			const JobCreateTimeRange = schemaComposer.createInputTC({
				name: 'JobCreateTimeDateRangeInput',
				fields: {
					startTime: 'Date',
					endTime: 'Date',
				},
			});

			const listPermissionCheck = gqcPermissionCheck({
				required: 'cloud-monitor\x01listJob',
				selfOnlyArgsBuilder: (_source, args, context) => {
					const uid = path(['request', 'auth', 'credentials', 'uid'], context);
					set(args, ['filter', 'uid'], uid);
				},
			});

			graphqlResolvers.Mutation.JobRemoveOne = graphqlResolvers.Mutation.JobRemoveOne.withMiddlewares(
				[removeJobById]
			);

			graphqlResolvers.Mutation.JobRemoveMany = graphqlResolvers.Mutation.JobRemoveMany.withMiddlewares(
				[removeJobByIds]
			);

			// #region pagination
			graphqlResolvers.Query.JobPagination = graphqlResolvers.Query.JobPagination.withMiddlewares(
				[listPermissionCheck]
			)
				.addFilterArg({
					name: 'verification',
					type: 'JobVerificatoinInput',
					description: 'verification',
					query: (query, value) => {
						Object.assign(query, nestedToOneLevelPath(value, 'verification'));
					},
				})
				.addFilterArg({
					name: 'uidIn',
					type: '[String]',
					description: '用户ID',
					query: (query, value) => {
						query.uid = { $in: value };
					},
				})
				.addFilterArg({
					name: 'targetSearch',
					type: 'String',
					description: '正则过滤: target.host',
					query: (query, value) => {
						query.targetUrl = {
							$regex: escapeRegExp(value.trim()),
							$options: 'i',
						};
					},
				})
				.addFilterArg({
					name: 'noteSearch',
					type: 'String',
					description: '正则过滤: note',
					query: (query, value) => {
						query.note = {
							$regex: escapeRegExp(value.trim()),
							$options: 'i',
						};
					},
				})
				.addFilterArg({
					name: 'target',
					type: 'JobTargetInput',
					description: 'target',
					query: (query, value) => {
						Object.entries(value).forEach(([field, value]) => {
							if (field === 'hostname') {
								query[`target.${field}`] = {
									$regex: escapeRegExp(value.trim()),
									$options: 'i',
								};
							} else {
								query[`target.${field}`] = value;
							}
						});
					},
				})
				.addFilterArg({
					name: 'createTimeRange',
					type: JobCreateTimeRange,
					description: '创建时间范围',
					query: (query, value) => {
						if (value.startTime) {
							set(query, ['createTime', '$gte'], value.startTime);
						}
						if (value.endTime) {
							set(query, ['createTime', '$lte'], value.endTime);
						}
					},
				})
				.addFilterArg({
					name: 'createTimeGTE',
					type: 'Date',
					description: '创建时间大于等于',
					query: (query, value) => {
						const fieldName = 'createTime';
						let field = query[fieldName] || {};
						if (typeof field !== 'object') field = {};
						query[fieldName] = { ...field, $gte: value };
					},
				})
				.addFilterArg({
					name: 'createTimeLTE',
					type: 'Date',
					description: '创建时间小于等于',
					query: (query, value) => {
						const fieldName = 'createTime';
						let field = query[fieldName] || {};
						if (typeof field !== 'object') field = {};
						query[fieldName] = { ...field, $lte: value };
					},
				})
				.addFilterArg({
					name: 'abnormalStatus',
					type: '[String]',
					description: '是否异常',
					query: (query, v) => {
						const [category, value] = v || [];
						const tasktypes = TASK_CATEGORY_TYPES_MAP[category];

						if (value === 'abnormal') {
							query.$or = tasktypes.map(type => ({
								[`${type}Abnormal`]: true,
							}));
						} else if (value === 'normal') {
							tasktypes.reduce((obj, type) => {
								obj[`${type}Abnormal`] = AVILIABLE_TASK_MAP[type]
									? { $ne: true }
									: false;
								return obj;
							}, query);
						}
					},
				})
				.addFilterArg({
					name: 'jobStatus',
					type: '[String]',
					description: '根据安全状态查询',
					query: (query, v) => {
						const [category, value] = v || [];
						const tasktypes = TASK_CATEGORY_TYPES_MAP[category];
						// 检测中： 只要有一个监测中
						// 威胁：存在威胁并且没有检测中
						// 安全：存在安全并且没有检测中和威胁
						// 未监测：全部都为未监测
						switch (value) {
							case 'started':
								query.$or = tasktypes.map(type => {
									return AVILIABLE_TASK_MAP[type]
										? {
												[`${type}IsStarted`]: {
													$elemMatch: {
														$eq: true,
													},
												},
										  }
										: {
												[`${type}IsStarted`]: true,
										  };
								});
								break;
							case 'warning':
								query.$and = [
									{
										$or: tasktypes.map(type => {
											return AVILIABLE_TASK_MAP[type]
												? {
														[`${type}SecurityStatus`]: {
															$elemMatch: {
																$eq: 'warning',
															},
														},
												  }
												: {
														[`${type}SecurityStatus`]: 'warning',
												  };
										}),
									},
								].concat(
									tasktypes.map(type => {
										return AVILIABLE_TASK_MAP[type]
											? {
													[`${type}IsStarted`]: {
														$nin: [true],
													},
											  }
											: {
													[`${type}IsStarted`]: {
														$ne: true,
													},
											  };
									})
								);
								break;
							case 'safe':
								query.$and = [
									{
										$or: tasktypes.map(type => {
											return AVILIABLE_TASK_MAP[type]
												? {
														[`${type}SecurityStatus`]: {
															$in: ['safe'],
														},
												  }
												: {
														[`${type}SecurityStatus`]: 'safe',
												  };
										}),
									},
								].concat(
									tasktypes.map(type => {
										return {
											[`${type}SecurityStatus`]: {
												$nin: ['warning'],
											},
											[`${type}IsStarted`]: {
												$nin: [true],
											},
										};
									})
								);
								break;

							default:
								tasktypes.reduce((obj, type) => {
									obj[`${type}SecurityStatus`] = AVILIABLE_TASK_MAP[type]
										? { $all: 'unknown' }
										: 'unknown';
									return obj;
								}, query);
								break;
						}
					},
				})
				.addFilterArg({
					name: 'assetSecurityStatus',
					type: '[String]',
					description: '资产安全状态筛选',
					query: (query, v) => {
						const fieldName = 'assetId';
						query[fieldName] = { $in: v };
					},
				});
			// #endregion

			// #region findMany
			graphqlResolvers.Query.JobMany = graphqlResolvers.Query.JobMany.withMiddlewares(
				[listPermissionCheck]
			)
				.addFilterArg({
					name: 'target',
					type: 'JobTargetInput',
					description: 'target',
					query: (query, value) => {
						Object.entries(value).forEach(([field, value]) => {
							query[`target.${field}`] = value;
						});
					},
				})
				.addFilterArg({
					name: 'noteSearch',
					type: 'String',
					description: '正则过滤: note',
					query: (query, value) => {
						query.note = {
							$regex: escapeRegExp(value.trim()),
							$options: 'i',
						};
					},
				})
				.addFilterArg({
					name: 'verification',
					type: 'JobVerificationInput',
					description: 'verification',
					query: (query, value) => {
						Object.assign(query, nestedToOneLevelPath(value, 'verification'));
					},
				});
			// #endregion

			// #region count
			graphqlResolvers.Query.JobCount = graphqlResolvers.Query.JobCount.withMiddlewares(
				[listPermissionCheck]
			);
			// #endregion

			// #region findOne
			graphqlResolvers.Query.JobOne = graphqlResolvers.Query.JobOne.withMiddlewares(
				[listPermissionCheck]
			);
			// #endregion
		},
	});

	tc.mongooseResolvers.createOne();
	tc.mongooseResolvers.updateOne();
	schemaComposer.Mutation.addFields({
		JobCreateOne: createOne,
		JobUpdateOne: updateJob,
	});
};
